//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include <sys/types.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <sys/times.h>

#include <errno.h>
#include <signal.h>

#include <unistd.h>

#include <fcntl.h>
#include <time.h>
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
#define NAMED_PIPE          "input"   
#define MAX_DELAY           5        
#define MAX_REQUEST_SIZE    100000       
#define CHANGE_DIRECTORY    0
#define WORKING_DIRECTORY   "working" 
#define MAX_CONCURRENT      4         
#define MAX_PARAMETERS      500000         
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
typedef char t_string [MAX_REQUEST_SIZE];
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
char      colorRed[]    = "\033[01;31m";
char      colorYellow[] = "\033[01;33m";
char      colorGreen[]  = "\033[01;32m";
char      colorEnd[]    = "\033[00m";
int       concServer    = 0;
int       childs        = 0;
int       stop          = 0;
int       fd;
int       fdSTDIN       = 0;
int       fdSTDOUT      = 1;
int       fdSTDDERROR   = 2;
t_string  inputOrders;
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void panic            (const char *str);
void delay            (void);
int  doCommand        (char* commandParametes[MAX_PARAMETERS]);
int  doExit           (void);
int  init             (void);
void readRequest      (int fd, t_string request);
void serveRequest     (t_string request);
void doQuit           (int sig);
void doTerm           (int sig);
void normalServer     (void);
void concurrentServer (void);
int  parseCommand     (t_string request, char* commandParametes[MAX_PARAMETERS]);
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int main (int argc, char *argv[]) {
  
  t_string s;
  int      i, process;

  switch (process = fork()) {

    case -1 : 
      panic("[ScheduleQueueForkProcess::main] Error in fork function");

    case 0  : {

      sprintf(s, "%s[%d] Background child created %d (launcher of process)\n%s", colorYellow, getpid(), process, colorEnd);
      if (write(1, s, strlen(s)) == -1) 
        panic("[ScheduleQueueForkProcess::concurrentServer] Error in write function");

      break;
      
    }

    default : {

      sprintf(s, "%s[%d] Closing main process \n%s", colorYellow, getpid(), colorEnd);
      if (write(1, s, strlen(s)) == -1) 
        panic("[ScheduleQueueForkProcess::concurrentServer] Error in write function");

      exit(EXIT_SUCCESS);
      
    }

  }
  
  strcpy(inputOrders, NAMED_PIPE);
  
  for (i = 1; i < argc; i++) {
    
    if (!strcmp(argv[i], "-c"))
      concServer = !concServer;
    if (!strcmp(argv[i], "-i")) {
      if (argv[i + 1]) 
        strcpy(inputOrders, argv[i + 1]);
      else {
        sprintf(s, "%s[%d] Error in the input file. You must enter the path after -i option\n%s", colorYellow, getpid(), colorEnd);
        if (write(1, s, strlen(s)) == -1) 
          panic("[ScheduleQueueForkProcess::main] Error in write function");
      }
    }
    
  }
  
  fd = init ();
  
  if (concServer) {
    
    sprintf(s, "%s[%d] Executing concurrent server to launch process from file\n%s", colorYellow, getpid(), colorEnd);
    if (write(1, s, strlen(s)) == -1) 
      panic("[ScheduleQueueForkProcess::main] Error in write function");

    signal(SIGUSR1, doQuit);
    signal(SIGUSR2, doTerm);
    
    concurrentServer();
    
  }
  else {
    
    sprintf(s, "%s[%d] Executing normal server to launch process from file\n%s", colorYellow, getpid(), colorEnd);
    if (write(1, s, strlen(s)) == -1) 
      panic("[ScheduleQueueForkProcess::main] Error in write function");
    
    normalServer();

  }
    
  return EXIT_SUCCESS;
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void panic (const char *str) {
  
  t_string s;

  write(2, str, strlen(str));
  write(2, "\n", 1);
  
  if (errno != 0) {
    sprintf(s, "%d %s\n", errno, strerror(errno));
    write(2, s, strlen(s));
  }
  
  exit(EXIT_FAILURE);
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void delay (void) {
  
  struct timespec t;

  t.tv_sec  = rand() % (MAX_DELAY + 1);
  t.tv_nsec = 0;
  
  nanosleep(&t, NULL);
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int doCommand (char* commandParametes [MAX_PARAMETERS]) {
  
  int      process, pid, status;
  t_string s;
  
  int i = 0;
  
  while (commandParametes[i])
    printf("%s ", commandParametes[i++]);
  printf("\n");

  sprintf(s, "%s[%d] command %s \n%s", colorGreen, getpid(), commandParametes[0], colorEnd);
  if (write(1, s, strlen(s)) == -1) 
    panic("[ScheduleQueueForkProcess::doCommand] Error in write function");

  delay();
  
  switch (process = fork())  {
    
    case -1 : 
      panic("[ScheduleQueueForkProcess::doCommand] Error in fork function");
             
    case 0  : {
      
      if (fdSTDIN  != 0) dup2(fdSTDIN,  STDIN_FILENO);
      if (fdSTDOUT != 1) dup2(fdSTDOUT, STDOUT_FILENO);
      
      if (!strstr(commandParametes[0], "/")) {
        execvp(commandParametes[0], commandParametes);
        panic("[ScheduleQueueForkProcess::doCommand] Error in execvp function");
      }
      else {
        execv(commandParametes[0], commandParametes);
        panic("[ScheduleQueueForkProcess::doCommand] Error in execv function");
      }
      
    }
             
    default : pid = wait(&status); break;
             
  }

  dup2(STDIN_FILENO,  STDIN_FILENO);
  dup2(STDOUT_FILENO, STDOUT_FILENO);
  
  if (WIFEXITED(status)) 
    return WEXITSTATUS(status);
  
  return EXIT_FAILURE;
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int doExit (void) {
  
  t_string s;

  sprintf(s, "%s[%d] Exiting\n%s", colorGreen, getpid(), colorEnd);
  if (write(1, s, strlen(s)) == -1) 
    panic("[ScheduleQueueForkProcess::doExit] Error in fork function");

  unlink(NAMED_PIPE);
  
  if (concServer) kill(getppid(), SIGUSR1);
  
  delay();
  
  exit(EXIT_SUCCESS);
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int init (void) {
  
  int ret, fd;

  unlink(inputOrders);
  
  ret = mknod(inputOrders, S_IFIFO | 0666, 0);
  
  if (ret == -1) 
    panic("[ScheduleQueueForkProcess::init] Error creating named pipe.");

  fd = open(inputOrders, O_RDWR);
  
  if (fd == -1) 
    panic("[ScheduleQueueForkProcess::init] Error opening named pipe");

  if (CHANGE_DIRECTORY)
    if (chdir(WORKING_DIRECTORY) == -1) 
      panic("[ScheduleQueueForkProcess::init] Error changing directory");

  srand(getpid());

  return fd;
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void readRequest (int fd, t_string request) {
  
  int ret, i = -1;

  do {
    
    i++;
    ret = read(fd, &request[i], 1);
    
    if (ret <= 0) 
      panic("[ScheduleQueueForkProcess::readRequest] Error in read function");
    
  }  while ((request[i] != '\n') && (i < MAX_REQUEST_SIZE));

  if (request[i] != '\n') 
    panic("[ScheduleQueueForkProcess::readRequest] Error. Too long request");

  request[i] = '\0';
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void serveRequest (t_string request) {
  
  char*    argv [MAX_PARAMETERS], newDir [MAX_REQUEST_SIZE];
  int      ret;
  t_string s;
  
  if (!strcmp(request, "QUIT")) {
    
    sprintf(s, "\n%s[%d] Good Bye (Server to launch process)....\n%s", colorRed, getpid(), colorEnd);
    if (write(1, s, strlen(s)) == -1) 
      panic("[ScheduleQueueForkProcess::serveRequest] Error in write function");
    
    exit(EXIT_SUCCESS);
    
  }
  
  if (!strncmp(request, "CHDIR", strlen("CHDIR"))) {
    
    ret = sscanf(request, "CHDIR %s", newDir);
    
    if (chdir(newDir) == -1) 
      panic("[ScheduleQueueForkProcess::serveRequest] Error changing directory");
    else {
      sprintf(s, "\n%s[%d] Changing directory to %s (Server to launch process)....\n%s", colorRed, getpid(), newDir, colorEnd);
      if (write(1, s, strlen(s)) == -1) 
        panic("[ScheduleQueueForkProcess::serveRequest] Error in write function");
    }
    
    ret = (ret == 1)?EXIT_SUCCESS:EXIT_FAILURE;        
    
  }
  else 
    ret = (parseCommand(request, argv) == EXIT_SUCCESS)?doCommand(argv):EXIT_FAILURE;

  
  if (ret == EXIT_FAILURE) {
    
    sprintf(s, "%s[%d] Syntax error\n%s", colorRed, getpid(), colorEnd);
    
    if (write(1, s, strlen(s)) == -1) 
      panic("[ScheduleQueueForkProcess::serveRequest] Error in write function");
    
  }

  sprintf(s, "\n%s[%d] Return code %d\n%s", colorGreen, getpid(), ret, colorEnd);
  
  if (write(1, s, strlen(s)) == -1) 
    panic("[ScheduleQueueForkProcess::serveRequest] Error in write function");
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void doQuit (int sig) {
  
  stop = 1;
  kill(getpid(), SIGUSR2);
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void doTerm (int sig) {
  
  int      pidChild, st;
  t_string s;
  
  pidChild = wait(&st);
  
  childs--;

  sprintf(s, "%s[%d] Child ended %d (%d launched process)\n%s", colorYellow, getpid(), pidChild, childs, colorEnd);
  if (write(1, s, strlen(s)) == -1) 
    panic("[ScheduleQueueForkProcess::doTerm] Error in write function");
 
  if (stop && !childs) {
    
    sprintf(s, "%s[%d] Exiting main server\n%s", colorYellow, getpid(), colorEnd);
    if (write(1, s, strlen(s)) == -1) 
      panic("[ScheduleQueueForkProcess::doTerm] Error in write function");
    
    exit(EXIT_SUCCESS);
  
  }
  
  signal(SIGUSR2, doTerm);
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void normalServer (void) {
  
  t_string request;
  
  while (1) {  
    
    readRequest(fd, request);
    serveRequest(request);
    
  }
  
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
void concurrentServer (void) {
  
  int      process;
  t_string request, s;
  
  while (1) {
    
    if (!stop) {
      
      if (childs < MAX_CONCURRENT) {
      
        readRequest(fd, request);

        switch (process = fork()) {

          case -1 : 
            panic("[ScheduleQueueForkProcess::concurrentServer] Error in fork function");

          case 0  : {

            serveRequest(request);
            
            kill(getppid(), SIGUSR2);
            
            exit(EXIT_FAILURE);

          }

          default : {

            childs++;

            sprintf(s, "%s[%d] Child created %d (%d launched process)\n%s", colorYellow, getpid(), process, childs, colorEnd);
            if (write(1, s, strlen(s)) == -1) 
              panic("[ScheduleQueueForkProcess::concurrentServer] Error in write function");

          }

        }
        
      }
      
    }
    else pause();
    
  }
  
}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
int parseCommand (t_string request, char* commandParametes[MAX_PARAMETERS]) {
  
  char*    parts = NULL, buffer [MAX_REQUEST_SIZE];
  int      argc = 0, concatCond = 0;
  
  printf("%s\n", request);
  
  parts = strtok(request, " \t\n\r");
  
  if (parts) {
    
    while (parts) {
      
      if (!concatCond) buffer[0] = '\0';
      
      concatCond = (parts[0] == '"')?1:concatCond;
      
      if (!concatCond || (concatCond && !strlen(buffer))) 
        sprintf(buffer, "%s\0",   parts);
      else                                                    
        sprintf(buffer, "%s %s\0", buffer, parts);
      
      concatCond = (parts[strlen(parts) - 1] == '"')?0:concatCond;
      
      if (!concatCond) {
        
        if (!strcmp(buffer, "RSTDIN")) {
          
          parts = strtok(NULL, " \t\n\r");
          if (!parts) break;
          
          fdSTDIN = open(parts, O_RDONLY);
          
        }
        else if (!strncmp(buffer, "RSTDOUT", strlen("RSTDOUT"))) {
          
          parts = strtok(NULL, " \t\n\r");
          if (!parts) break;
          
          fdSTDOUT = open(parts, O_CREAT | O_WRONLY | O_TRUNC, 0644);
          
        }
        else {
          
          //printf("PARAMETERS: %s\n", buffer);

          commandParametes[argc] = new char[strlen(buffer) + 1];
          strcpy(commandParametes[argc], ((buffer[0] == '"')?(buffer + 1):buffer));

          if ((commandParametes[argc][strlen(commandParametes[argc]) - 1] == '"'))
            commandParametes[argc][strlen(commandParametes[argc]) - 1] = '\0';

          argc++;

        }
        
      }
      
      parts = strtok(NULL, " \t\n\r");
      
    }
    
    commandParametes[argc] = NULL;
    
  }
  else return EXIT_FAILURE;
  
  return EXIT_SUCCESS;

}
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
//---------------------------------------------------------------------------------------------------------------------------------------------------------//
/*
 * weka.filters.unsupervised.attribute.Remove -R 1
 * weka.attributeSelection.ConsistencySubsetEval -s "weka.attributeSelection.LinearForwardSelection -D 0 -N 5 -I -K 50 -T 0"
 * weka.attributeSelection.CfsSubsetEval -s "weka.attributeSelection.LinearForwardSelection -D 0 -N 5 -I -K 50 -T 0"
 * 
 */